#define FILE_ID dokan

#include "fs_win.h"
#include "provider_api.h"
#include "utils.h"
#include "log.h"



int ErrToWin32Dokan(int err);

DokanFilesystemMount::DokanFilesystemMount(FilesystemProvider *provider, const std::wstring& mount, const Configuration& cfg)
	: Provider(provider), Mount(mount), IsMounted(false), SerialNumber(0), RootDir(0)
{
	FriendlyName = Utf8To16(cfg.LookupSingle("name"));
	MountWait = CreateEvent(NULL, TRUE, FALSE, NULL);
}

DokanFilesystemMount::~DokanFilesystemMount()
{
	CloseHandle(MountWait);
	Unmount();
}

void DokanFilesystemMount::Unmount()
{
	DokanRemoveMountPoint(Mount.c_str());
}

int DokanFilesystemMount::CreateFile(const std::string& FileName, DWORD DesiredAccess, DWORD ShareMode, DWORD CreationDisposition, DWORD FlagsAndAttributes, PDOKAN_FILE_INFO info)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }

	if((FlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS) != 0) {
		//we don't support opening with backup semantics
		return -ERROR_ACCESS_DENIED;
	}

	ProviderFile *f;
	ProviderDir *d;
	int err = Provider->OpenFile(&f, &d, FileName, (OpenType)CreationDisposition, (FlagsAndAttributes & FILE_ATTRIBUTE_READONLY) != 0, (DesiredAccess & (GENERIC_WRITE|FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
	if(err)
		return ErrToWin32Dokan(err);

	if(f) {
		info->Context = (size_t)(ProviderCommon*)f;
		info->IsDirectory = FALSE;
	} else {
		info->Context = (size_t)(ProviderCommon*)d;
		info->IsDirectory = TRUE;
	}

	return 0;
}

int DokanFilesystemMount::OpenRootDir(PDOKAN_FILE_INFO info)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }
	if(!RootDir) {
		RootDirOpenLock.Acquire();
		if(!RootDir) {
			ProviderDir *d;
			if(!Provider->OpenFile(NULL, &d, "/", OpenOpenExisting, false, false))
				RootDir = d;
		}
		RootDirOpenLock.Release();
	}
	ProviderDir *root = RootDir;
	if(!root)
		return -ERROR_ACCESS_DENIED;
	root->Reference();
	info->Context = (size_t)root;
	return 0;
}

int DokanFilesystemMount::OpenDirectory(const std::string& FileName, PDOKAN_FILE_INFO info)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }

	ProviderDir *d;
	int err = Provider->OpenFile(NULL, &d, FileName, OpenOpenExisting, false, false);
	if(err)
		return ErrToWin32Dokan(err);

	info->Context = (size_t)d;
	info->IsDirectory = TRUE;

	return 0;
}
int DokanFilesystemMount::CreateDirectory(const std::string& FileName, PDOKAN_FILE_INFO info)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }

	ProviderDir *d;
	int err = Provider->OpenFile(NULL, &d, FileName, OpenCreateNew, false, true);

	if(err)
		return ErrToWin32Dokan(err);

	info->Context = (size_t)d;
	info->IsDirectory = TRUE;

	return 0;
}
int DokanFilesystemMount::Cleanup(PDOKAN_FILE_INFO info)
{
	return ERROR_SUCCESS;
}

int DokanFilesystemMount::Close(PDOKAN_FILE_INFO info)
{
	LOG(LogInfo, "Handle %p closed", (void*)info->Context);
	GetCommonObj(info)->Dereference();
	info->Context = NULL;
	return ERROR_SUCCESS;
}

int DokanFilesystemMount::Move(const std::string& from, const std::string& to, int replace)
{
	int ret = Provider->Rename(from, to, false);
	return ErrToWin32Dokan(ret);
}

int DokanFilesystemMount::GetDiskFreeSpace(PULONGLONG  FreeBytesAvailable, PULONGLONG  TotalNumberOfBytes, PULONGLONG  TotalNumberOfFreeBytes)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }
	Provider->GetDiskInfo(*TotalNumberOfFreeBytes, *FreeBytesAvailable, *TotalNumberOfBytes);
	return 0;
}

int DokanFilesystemMount::GetVolumeInformation(std::wstring& friendlyname, std::wstring& fstype, DWORD& serial, DWORD& componentlength, DWORD& flags)
{
	if(!IsMounted) { IsMounted = true; SetEvent(MountWait); }
	friendlyname = FriendlyName;
	fstype = Utf8To16(Provider->GetFsType());
	serial = SerialNumber;
	componentlength = 255;
	flags = FILE_CASE_PRESERVED_NAMES | FILE_CASE_SENSITIVE_SEARCH;
	return ERROR_SUCCESS;
}

int DokanFilesystemMount::GetFileInformation(LPBY_HANDLE_FILE_INFORMATION Buffer, PDOKAN_FILE_INFO info)
{
	ProviderCommon *common = GetCommonObj(info);
	if(!common)
		return -ERROR_INVALID_HANDLE;

	memset(Buffer, 0, sizeof(BY_HANDLE_FILE_INFORMATION));
	Buffer->nNumberOfLinks = 1;
	Buffer->dwVolumeSerialNumber = SerialNumber;

	const DirStatInfo& stat = common->Stat();
	Buffer->nFileSizeHigh = (DWORD)(stat.FileSize >> 32);
	Buffer->nFileSizeLow = (DWORD)(stat.FileSize & 0xFFFFFFFF);
	Buffer->ftLastWriteTime.dwHighDateTime = (DWORD)(stat.WriteTime >> 32);
	Buffer->ftLastWriteTime.dwLowDateTime = (DWORD)(stat.WriteTime & 0xFFFFFFFF);
	Buffer->ftCreationTime.dwHighDateTime = (DWORD)(stat.CreationTime >> 32);
	Buffer->ftCreationTime.dwLowDateTime = (DWORD)(stat.CreationTime & 0xFFFFFFFF);
	Buffer->dwFileAttributes = GetAttributesFor(stat);

	return ERROR_SUCCESS;
}

